package com.wobito.common.config;

import com.alibaba.fastjson.JSON;
import com.wobito.common.constants.ErrorCode;
import com.wobito.common.constants.SysConstants;
import com.wobito.common.filter.JsonLoginFilter;
import com.wobito.common.filter.TokenLoginFilter;
import com.wobito.common.utils.JwtUtil;
import com.wobito.common.utils.ResultUtils;
import com.wobito.common.utils.StringUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Configuration
@EnableMethodSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    @Resource
    private UserDetailsService userDetailsService;

    @Resource
    private TokenLoginFilter tokenLoginFilter;

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().antMatchers("/swagger-ui.html",
                "/swagger-ui/*",
                "/swagger-resources/**",
                "/webjars/**");
    }


    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // qa
//                .antMatchers("/**").permitAll()
                //prod
                .antMatchers("/login","/smartBookApi/**","/operate/**","/websocket/**").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .cors()
                .and()
                .formLogin()
                .permitAll()
                .and()
                /*设置会话创建策略为无状态。*/
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                /*配置无权限访问的异常处理器*/
                .exceptionHandling()
                .accessDeniedHandler(
                        (HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) -> {
                            writeResp(ResultUtils.error(ErrorCode.NO_AUTH, "无权限访问"), response);
                        }
                )
                /*配置认证失败的异常处理器*/
                .authenticationEntryPoint(
                        (HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) -> {
                            writeResp(ResultUtils.error(ErrorCode.NOT_LOGIN, "系统错误"), response);
                        }
                )
                .and()
                //替换认证过滤器
                .addFilterAt(jsonLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                //tokenLoginFilter 要放 JsonLoginFilter.class 因为我们在认证之前 JsonLoginFilter.class 已经解析好了
                //之后我们就不需要它了
                .addFilterAt(tokenLoginFilter, JsonLoginFilter.class)
                //用于禁用跨站请求伪造（CSRF）保护。
                .csrf().disable();

        return http.build();
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Bean
    public AuthenticationManager authenticationManager() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        return new ProviderManager(daoAuthenticationProvider);
    }

    /*采用JSON方式登录*/
    @Bean
    public JsonLoginFilter jsonLoginFilter() {
        JsonLoginFilter jsonLoginFilter = new JsonLoginFilter();
        jsonLoginFilter.setAuthenticationManager(authenticationManager());
        jsonLoginFilter.setAuthenticationFailureHandler((request, response, exception) -> {
            Map<String, Object> result = new HashMap<>();
            result.put("code", 400);
            result.put("msg", "登录失败");
            if (StringUtils.isEmpty(exception.getMessage())){
                result.put("desc", "未查询到账户信息");
            }else if (exception.getMessage().equals(SysConstants.SYSTEM_NO_AUTH)){
                result.put("desc", "用户账号无权限或已被锁定");
            }else{
                result.put("desc", exception.getMessage());
            }
            writeResp(result, response);
        });
        jsonLoginFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            List<String> permissions = authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
            String token = JwtUtil.createAccessToken(authentication.getName(), permissions);
            Map<String, Object> result = new HashMap<>();
            result.put("code", 200);
            result.put("msg", "登录成功");
            result.put("token", token);
            writeResp(result, response);
        });
        return jsonLoginFilter;
    }

    public void writeResp(Object content, HttpServletResponse response) {
        response.setContentType("application/json;charset=utf-8");
        try {
            response.getWriter().write(JSON.toJSONString(content));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
